package com.allen.study.test_common.utils.jwt;

import cn.hutool.core.convert.Convert;
import com.allen.study.test_common.constant.SecurityConstants;
import com.allen.study.test_common.exception.CustomRuntimeException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * @ClassName: JwtUtils
 * @Author: AllenSun
 * @Date: 2025/3/7 20:35
 *
 * 全局异常处理器（通常使用 @ControllerAdvice 和 @ExceptionHandler 注解）主要处理控制器方法调用过程中抛出的异常。
 * 要是 JWT 异常在过滤器或者拦截器中抛出，且这些组件不在控制器方法调用链里，全局异常处理器就无法捕获这些异常。
 */
@Component
@Slf4j
public class JwtUtils {
    // 秘钥
    private static final String SECRET_KEY = "123456654321";  // 建议存储于环境变量

    // 过期时间
    private static final long EXPIRATION_MS = 86400000;  // 24小时


    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    // 创建一个token，参数为claims和subject
    private String createToken(Map<String, Object> claims, String subject) {
        // 使用Jwts.builder()创建一个token
        return Jwts.builder()
                // 设置token的claims
                .setClaims(claims)
                // 设置token的主题
                .setSubject(subject)
                // 设置token的签发时间
                .setIssuedAt(new Date(System.currentTimeMillis()))
                // 设置token的过期时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS * 1000))
                // 使用HS256算法和SECRET_KEY对token进行签名
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                // 将token压缩成一个字符串
                .compact();
    }

    // 验证token是否有效，参数为token和userDetails
    public Boolean validateToken(String token, UserDetails userDetails) {
        // 从token中提取用户名
        final String username = extractUsername(token);
        // 返回用户名是否与userDetails中的用户名相等，并且token是否未过期
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    // 从token中提取用户名
    public String extractUsername(String token) {
        // 从token中提取声明，并返回用户名
        return extractClaim(token, Claims::getSubject);
    }

    // 从token中提取过期时间
    public Date extractExpiration(String token) {
        // 调用extractClaim方法，传入token和Claims::getExpiration方法引用
        return extractClaim(token, Claims::getExpiration);
    }

    // 从token中提取指定类型的声明
    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        // 从token中提取所有的claims
        final Claims claims = extractAllClaims(token);
        // 使用claimsResolver函数处理claims，并返回结果
        return claimsResolver.apply(claims);
    }

    // 从token中提取所有声明
    public Claims extractAllClaims(String token) {
        // 使用SECRET_KEY解析token，并返回声明
        Claims claims = null;
        try {
            claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
        } catch (SignatureException e) {
            log.info("无效的JWT签名：{}",e.getMessage());
            throw new CustomRuntimeException("无效的JWT签名，请重新登录");
        }

        return claims;
    }

    // 判断token是否过期
    public Boolean isTokenExpired(String token) {
        // 提取token中的过期时间
        return extractExpiration(token).before(new Date());
    }


    /**
     * 根据令牌获取用户标识
     *
     * @param token 令牌
     * @return 用户ID
     */
    public String getUserKey(String token)
    {
        Claims claims = extractAllClaims(token);
        return getValue(claims, SecurityConstants.USER_KEY);
    }

    /**
     * 根据身份信息获取键值
     *
     * @param claims 身份信息
     * @param key 键
     * @return 值
     */
    public String getValue(Claims claims, String key)
    {
        return Convert.toStr(claims.get(key), "");
    }


    /**
     * 根据令牌获取用户标识
     *
     * @param claims 身份信息
     * @return 用户ID
     */
    public String getUserKey(Claims claims)
    {
        return getValue(claims, SecurityConstants.USER_KEY);
    }

    /**
     * 根据身份信息获取用户ID
     *
     * @param claims 身份信息
     * @return 用户ID
     */
    public String getUserId(Claims claims)
    {
        return getValue(claims, SecurityConstants.DETAILS_USER_ID);
    }

    /**
     * 根据令牌获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUserName(String token)
    {
        Claims claims = extractAllClaims(token);
        return getValue(claims, SecurityConstants.DETAILS_USERNAME);
    }

    /**
     * 根据身份信息获取用户名
     *
     * @param claims 身份信息
     * @return 用户名
     */
    public String getUserName(Claims claims)
    {
        return getValue(claims, SecurityConstants.DETAILS_USERNAME);
    }

}
